package game.dungeon.position;


import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;

/**
 * An immutable class of positions used to describe the position of an object in a three dimensional axes.
 *  
 * @version 1.0
 * @author Chris Desomer, Geert Heyman
 */
public class Position {
	
	/**
	 * Initialize the position by setting the x-, y- and z-coordinate to the given values.
	 * @param 	x
	 * 			the new x-coordinate for this position
	 * @param 	y
	 * 			the new y-coordinate for this position
	 * @param 	z
	 * 			the new z-coordinate for this position
	 */
	public Position (int x, int y, int z){
		xCoordinate = x;
		yCoordinate = y;
		zCoordinate = z;
	}
	
	
	/**
	 * Return the x-coordinate of this position.
	 */
	@Basic @Immutable
	public int getXCoordinate() {
		return xCoordinate;
	}

	private final int xCoordinate;
	
	/**
	 * Return the y-coordinate of this position.
	 */
	@Basic @Immutable
	public int getYCoordinate() {
		return yCoordinate;
	}

	private final int yCoordinate;

	/**
	 * Return the z-coordinate of this position.
	 */
	@Basic @Immutable
	public int getZCoordinate() {
		return zCoordinate;
	}

	private final int zCoordinate;
	
	/**
	 * Return the coordinate opf this position corresponding
	 * to the given coordinate type.
	 * 
	 * @param	coordinateType
	 * 			The coordinate type to get the coordinate from.
	 * @return	..
	 * 		  | if (coordinateType == CoordinateType.XCOORDINATE)
	 * 		  |		then result == getXCoordinate()
	 * @result	..
	 * 		  | else if (coordinateType == CoordinateType.YCOORDINATE)
	 * 		  |		then result == getYCoordinate()
	 * @result	..
	 * 		  | else if (coordinateType == CoordinateType.ZCOORDINATE)
	 * 		  |		then result == getZCoordinate()			
	 */
	public int getCoordinate(CoordinateType coordinateType){
		if (coordinateType == CoordinateType.XCOORDINATE)
			return xCoordinate;
		if (coordinateType == CoordinateType.YCOORDINATE)
			return yCoordinate;
		return zCoordinate;
	}
	/**
	 * Get the position that borders the position in the given direction.
	 * 
	 * @param 	direction
	 * 			The direction to get the position in. 
	 * @pre		...
	 * 		  | direction != null
	 * @return	...
	 * 		  |	result.getClass() == this.getClass()
	 * @result	...
	 * 		  | if (direction == Direction.CEILING)
	 *		  |		result.getXCoordinate() == this.getXCoordinate() &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate() &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()+1
	 * @result	...
	 * 		  | if (direction == Direction.FLOOR)
	 *		  |		result.getXCoordinate() == this.getXCoordinate() &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate() &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()-1
	 * @result	...
	 * 		  | if (direction == Direction.NORTH)
	 *		  |		result.getXCoordinate() == this.getXCoordinate() &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate()+1 &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()
	 * @result	...
	 * 		  | if (direction == Direction.SOUTH)
	 *		  |		result.getXCoordinate() == this.getXCoordinate() &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate()-1 &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()
	 * @result	...
	 * 		  | if (direction == Direction.EAST)
	 *		  |		result.getXCoordinate() == this.getXCoordinate()+1 &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate() &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()
	 * @result	...
	 * 		  | if (direction == Direction.WEST)
	 *		  |		result.getXCoordinate() == this.getXCoordinate()-1 &&
	 *		  |		result.getYCoordinate() == this.getYCoordinate() &&
	 *		  |		result.getZCoordinate() == this.getZCoordinate()
	 *				
	 */
	public Position getPositionInDirection(Direction direction){
		assert (direction !=null);
		if (direction == Direction.CEILING)
			return new Position(xCoordinate, yCoordinate, zCoordinate+1);
		if (direction == Direction.FLOOR)
			return new Position(xCoordinate, yCoordinate, zCoordinate-1);
		if (direction == Direction.NORTH)
			return new Position(xCoordinate, yCoordinate+1, zCoordinate);
		if (direction == Direction.SOUTH)
			return new Position(xCoordinate, yCoordinate-1, zCoordinate);
		if (direction == Direction.EAST)
			return new Position(xCoordinate+1, yCoordinate, zCoordinate);
		return new Position(xCoordinate-1, yCoordinate, zCoordinate);		
	}
	
	/**
	 * Return a string representation of the position.
	 * 
	 * @return	..
	 * 		  | result == "Position [xCoordinate=" + getXCoordinate() + ", yCoordinate="
	 *		  |		+ getYCoordinate() + ", zCoordinate=" + getZCoordinate() + "]"
	 */
	@Override
	public String toString() {
		return "Position [xCoordinate=" + xCoordinate + ", yCoordinate="
				+ yCoordinate + ", zCoordinate=" + zCoordinate + "]";
	}


	/**
	 * Get a new position with the given coordinates.
	 * 
	 * @param 	xCoordinate
	 * 			The x-coordinate for the new position.
	 * @param 	yCoordinate
	 * 			The y-coordinate for the new position.
	 * @param 	zCoordinate
	 * 			The y-coordinate for the new position.
	 * @return	...
	 * 		  | result.getClass() == this.getClass() &&
	 * 		  | result.getXCoordinate() == xCoordinate &&
	 * 		  | result.getYCoordinate() == yCoordinate &&
	 * 		  | result.getZCoordinate() == zCoordinate
	 */
	public static Position getPosition(int xCoordinate, int yCoordinate, int zCoordinate){
		return new Position(xCoordinate,yCoordinate,zCoordinate);
	}
	
	
	/**
	 * Return the hash code value for this position object.
	 * 
	 * @return	The hash code value for this position object.
	 * 		  | result == 31 * (31 * (31 + getXCoordinate()) + getYCoordinate()) + getZCoordinate()
	 * 
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + xCoordinate;
		result = prime * result + yCoordinate;
		result = prime * result + zCoordinate;
		return result;
	}


	/**
	 * Indicate whether some other object is "equal to" this one.
	 * 
	 * @return	..
	 * 		  | if (this == obj)
	 * 		  |		then result == true
	 * @result	..
	 * 		  | if (obj == null)
	 * 		  | 	then result == false
	 * @result	..
	 * 		  | if( getClass() != obj.getClass())
	 * 		  | 	then result == false
	 * @result	..
	 * 		  | if( getXCoordinate() != (Position) obj.getXCoordinate() )
	 * 		  |		then result == false
	 * @result	..
	 * 		  | if( getYCoordinate() != (Position) obj.getYCoordinate() )
	 * 		  |		then result == false
	 * @result	..
	 * 		  | if( getZCoordinate() != (Position) obj.getZCoordinate() )
	 * 		  |		then result == false
	 * @result ..
	 * 		  | else result == true
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Position other = (Position) obj;
		if (xCoordinate != other.xCoordinate)
			return false;
		if (yCoordinate != other.yCoordinate)
			return false;
		if (zCoordinate != other.zCoordinate)
			return false;
		return true;
	}
	
	/**
	 * Check if this position is located within the boundary defined by
	 * the given position : by drawing as square with one vertice in the 
	 * origin position and another vertice in the given position.
	 * 
	 * @return	..
	 * 		  | if (getXCoordinate() > position.getXCoordinate())
	 * 		  |		then result == false
	 * @result	..
	 * 		  | else if (getYCoordinate() > position.getYCoordinate())
	 * 		  |		then result == false
	 * @result	..
	 * 		  | else result == getZCoordinate <= position.getZCoordinate()
	 */
	public boolean isWithinBoundaryOf(Position position){
		if (getXCoordinate() > position.getXCoordinate())
			return false;
		if (getYCoordinate() > position.getYCoordinate())
			return false;
		return getZCoordinate() <= position.getZCoordinate();
	}
	
	/**
	 * Get a position with coordinates that are the sum of the coordinates
	 * of this position and the given position.
	 *  
	 * @param 	position
	 * 			The position to add to this position
	 * @return	A new position with a coordinates that are the sum of the coordinates
	 * 			of this position and the given position.
	 * 
	 */ 
	public Position add (Position position){
		int xCoordinate = getXCoordinate() + position.getXCoordinate();
		int yCoordinate = getYCoordinate() + position.getYCoordinate();
		int zCoordinate = getZCoordinate() + position.getZCoordinate();
		return new Position(xCoordinate, yCoordinate, zCoordinate);
	}
	
	/**
	 * Get a position with coordinates that are the substraction for the coordinates
	 * of this position and the coordinates of the given position.
	 * 
	 * @param 	position
	 * @return	A new position with a coordinates that are the substraction of the coordinates
	 * 			of this position and the given position.
	 */
	public Position substract(Position position){
		int xCoordinate = getXCoordinate() - position.getXCoordinate();
		int yCoordinate = getYCoordinate() - position.getYCoordinate();
		int zCoordinate = getZCoordinate() - position.getZCoordinate();	
		return new Position(xCoordinate, yCoordinate, zCoordinate);
	}
	
	/**
	 * Check if first cube (defined by its given origin and boundary position),
	 * is disjunct with the second cube (als defined by its given origin and 
	 * boundary position).
	 * 
	 * @param	originFirstCube
	 * 			The position that represents the origin vertice of the first cube.
	 * @param	boundaryPositionFirstCube
	 * 			The position that represents the vertice of the first cube the furthest
	 * 			away from the given origin.
	 * @param	originSecondCube
	 * 			The position that represents the origin vertice of the second cube.
	 * @param	boundaryPositionSecondCube	
	 * 			The position that represents the vertice of the first cube the furthest
	 * 			away from the given origin.
	 * @return	True if and only if the cubes defined by the given positions don't have 
	 * 			any positions incommon.	 
	 */
	public static boolean areDisjunctCubes(Position originFirstCube,
			Position boundaryPositionFirstCube, Position originSecondCube,
			Position boundaryPositionSecondCube) {
		for (CoordinateType coordinate : CoordinateType.values())
			if (areDisjunctIntervals(originFirstCube.getCoordinate(coordinate),
					boundaryPositionFirstCube.getCoordinate(coordinate), originSecondCube
							.getCoordinate(coordinate), boundaryPositionSecondCube
							.getCoordinate(coordinate)))
				return true;
		return false;
	}

	/*
	 * Check if the intervals represented by the four given integers are disjunct.
	 */
	private static boolean areDisjunctIntervals(int smallCoordinateFirst, int largeCoordinateFirst,
			int smallCoordinateSecond, int largeCoordinateSecond) {
		if (smallCoordinateFirst <= largeCoordinateSecond && largeCoordinateFirst >= smallCoordinateSecond)
			return false;
		if (smallCoordinateFirst >= smallCoordinateSecond && smallCoordinateFirst <= largeCoordinateSecond)
			return false;
		return true;
	}
	
	/**
	 * Check if the given position has at least one coordinate that is the same
	 * as the corresponding coordinate of this position.
	 * @param 	position
	 * 			The position to check with.
	 * @return 	True if and only if at least one of the coordinates of the 
	 * 			given position is the same as the corresponding coordinate
	 * 			of this position.
	 */
	public boolean hasEqualCoordinateAs(Position position) {
		if (getXCoordinate() == position.getXCoordinate())
			return true;
		if (getYCoordinate() == position.getYCoordinate())
			return true;
		return getZCoordinate() == position.getZCoordinate();
	}
}
